/**
 * $version:  0.1 
 * $Date: 2012-03-19 
 *
 * Copyright (C) 2010-2011 Jawa Software. All rights reserved.
 *
 */

package org.jawa.core.container;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import java.util.zip.ZipFile;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.jawa.core.JawaServer;
import org.jawa.core.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 加载插件,在<tt>plugins</tt>目录监控任何新的插件,进行实时的加载.
 *
 */
public class PluginManager {

	private static final Logger Log = LoggerFactory.getLogger(PluginManager.class);

	private File pluginDirectory;
	private Map<String, Plugin> plugins;
	private Map<Plugin, PluginClassLoader> classloaders;
	private Map<Plugin, File> pluginDirs;
	/**
	 * 保持跟踪插件的名字以及它们的解压缩文件. 
	 * 插件解压后并没有被加载时，更新此列表。
	 */
	private Map<String, File> pluginFiles;
	private ScheduledExecutorService executor = null;
	private Map<Plugin, PluginDevEnvironment> pluginDevelopment;
	private Map<Plugin, List<String>> parentPluginMap;
	private Map<Plugin, String> childPluginMap;
	private Set<String> devPlugins;
	private PluginMonitor pluginMonitor;
	private Set<PluginListener> pluginListeners = new CopyOnWriteArraySet<PluginListener>();
	private Set<PluginManagerListener> pluginManagerListeners = new CopyOnWriteArraySet<PluginManagerListener>();

	/**
	 * 初始化一个插件管理器.
	 *
	 * @param pluginDir 插件的目录.
	 */
	public PluginManager(File pluginDir) {
		this.pluginDirectory = pluginDir;
		plugins = new ConcurrentHashMap<String, Plugin>();
		pluginDirs = new HashMap<Plugin, File>();
		pluginFiles = new HashMap<String, File>();
		classloaders = new HashMap<Plugin, PluginClassLoader>();
		pluginDevelopment = new HashMap<Plugin, PluginDevEnvironment>();
		parentPluginMap = new HashMap<Plugin, List<String>>();
		childPluginMap = new HashMap<Plugin, String>();
		devPlugins = new HashSet<String>();
		pluginMonitor = new PluginMonitor();
	}

	/**
	 * 启动所有插件与插件监视器.
	 */
	public void start() {
		executor = new ScheduledThreadPoolExecutor(1);
		if (Boolean.getBoolean("developmentMode")) {
			executor.scheduleWithFixedDelay(pluginMonitor, 0, 5, TimeUnit.SECONDS);
		} else {
			executor.scheduleWithFixedDelay(pluginMonitor, 0, 20, TimeUnit.SECONDS);
		}
	}

	/**
	 * 停止所有的插件以及插件临视器.
	 */
	public void shutdown() {
		// 停止插件临视器.
		if (executor != null) {
			executor.shutdown();
		}
		// 停止所有插件.
		for (Plugin plugin : plugins.values()) {
			try {
				plugin.destroyPlugin();
			} catch (Exception e) {
				Log.error(e.getMessage(), e);
			}
		}
		plugins.clear();
		pluginDirs.clear();
		pluginFiles.clear();
		classloaders.clear();
		pluginDevelopment.clear();
		childPluginMap.clear();
		pluginMonitor = null;
	}

	/**
	 * 安装或更新指定的插件.
	 *
	 * @param in 包含一个新的插件定义的输入流.
	 * @param pluginFilename 要安装或更新的插件.
	 * @return true 是否安装或更新成功.
	 */
	public boolean installPlugin(InputStream in, String pluginFilename) {
		if (in == null || pluginFilename == null || pluginFilename.length() < 1) {
			Log.error("安装插件成功: 输入流没有找到或是长度不够.");
			return false;
		}
		try {
			byte[] b = new byte[1024];
			int len;
			int index = pluginFilename.lastIndexOf(File.separator);
			if (index != -1) {
				pluginFilename = pluginFilename.substring(index + 1);
			}
			String absolutePath = pluginDirectory + File.separator + pluginFilename;
			OutputStream out = new FileOutputStream(absolutePath + ".part");
			while ((len = in.read(b)) != -1) {
				out.write(b, 0, len);
			}
			out.close();
			// 删除旧文件
			new File(absolutePath).delete();
			// 改名为新文件
			new File(absolutePath + ".part").renameTo(new File(absolutePath));
			// 告诉监视器可以更新了.
			pluginMonitor.run();
		} catch (IOException e) {
			Log.error("安装插件时发生错误: " + pluginFilename, e);
			return false;
		}
		return true;
	}

	/**
	 * 指定的插件文件已存丰.
	 *
	 * @param pluginFilename 插件名称.
	 * @return true 插件的相关更新文件已存在.
	 */
	public boolean isPluginDownloaded(String pluginFilename) {
		return new File(pluginDirectory + File.separator + pluginFilename).exists();
	}

	/**
	 * 返回所有已安装的插件.
	 *
	 * @return 所有已安装的插件.
	 */
	public Collection<Plugin> getPlugins() {
		return Collections.unmodifiableCollection(plugins.values());
	}

	/**
	 * 返回指定名称的插件.
	 *
	 * @param name 插件名称.
	 * @return the 插件.
	 */
	public Plugin getPlugin(String name) {
		return plugins.get(name);
	}

	/**
	 * 返回插件的目录.
	 *
	 * @param plugin 插件.
	 * @return the 插件目录.
	 */
	public File getPluginDirectory(Plugin plugin) {
		return pluginDirs.get(plugin);
	}

	/**
	 * 返回插件的安装文件.
	 *
	 * @param name 插件名称.
	 * @return the 插件的安装文件.
	 */
	public File getPluginFile(String name) {
		return pluginFiles.get(name);
	}

	/**
	 * 加载插件时至少尝试过一次时则返回true
	 * <p>返回true并不表示这个插件被加载并且可用，也不代表未来将被加载的插件已被加载.<p>
	 *
	 * @return true 加载插件时至少尝试过一次时则返回true
	 */
	public boolean isExecuted() {
		return pluginMonitor.executed;
	}

	/**
	 * 加载到容器中的一个插件模块。加载由以下步骤
	 * <p/>
	 * <li>加载所有在lib包中存在的JAR文件</li>
	 * <li>加载所有在classes中存在的目录</li>
	 * <li>查找<tt>module.xml</tt> 并加载到<tt>context</tt></li>
	 * <li>对于每个 jawa.module 条目中 给定的类将作为一个模块加载，并启动它</li>
	 * <p/>
	 * </ul>
	 *
	 * @param pluginDir 插件目录.
	 */
	private void loadPlugin(File pluginDir) {
		// 仅在设置模式下加载管理员插件。
		if (JawaServer.getInstance().isSetupMode() && !(pluginDir.getName().equals("admin"))) {
			return;
		}
		Log.debug("PluginManager: 加载插件 " + pluginDir.getName());
		Plugin plugin;
		try {
			File pluginConfig = new File(pluginDir, "plugin.xml");
			if (pluginConfig.exists()) {
				SAXReader saxReader = new SAXReader();
				saxReader.setEncoding("UTF-8");
				Document pluginXML = saxReader.read(pluginConfig);

				//指定插件运行所需的JawaServer的版本。
				Element minServerVersion = (Element) pluginXML.selectSingleNode("/plugin/minServerVersion");
				if (minServerVersion != null) {
					String requiredVersion = minServerVersion.getTextTrim();
					Version version = JawaServer.getInstance().getServerInfo().getVersion();
					String hasVersion = version.getMajor() + "." + version.getMinor() + "." + version.getMicro();
					if (hasVersion.compareTo(requiredVersion) < 0) {
						String msg = "忽略插件 " + pluginDir.getName() + ": requires " + "服务器版本"
								+ requiredVersion;
						Log.warn(msg);
						System.out.println(msg);
						return;
					}
				}

				PluginClassLoader pluginLoader;

				// 检查是否为别一个插件的子插件. 如果是，我们重新使用父插件的类加载器，以便可以进行交互
				Element parentPluginNode = (Element) pluginXML.selectSingleNode("/plugin/parentPlugin");

				String pluginName = pluginDir.getName();
				String webRootKey = pluginName + ".webRoot";
				String classesDirKey = pluginName + ".classes";
				String webRoot = System.getProperty(webRootKey);
				String classesDir = System.getProperty(classesDirKey);

				if (webRoot != null) {
					final File compilationClassesDir = new File(pluginDir, "classes");
					if (!compilationClassesDir.exists()) {
						compilationClassesDir.mkdir();
					}
					compilationClassesDir.deleteOnExit();
				}

				if (parentPluginNode != null) {
					String parentPlugin = parentPluginNode.getTextTrim();
					// 检查父插件是否已加载.
					if (plugins.containsKey(parentPlugin)) {
						pluginLoader = classloaders.get(getPlugin(parentPlugin));
						pluginLoader.addDirectory(pluginDir, classesDir != null);

					} else {
						// 检查父插件是否存在并且还没有被加载.
						if (pluginName.compareTo(parentPlugin) < 0) {
							// 检查父插件是否存在
							File file = new File(pluginDir.getParentFile(), parentPlugin + ".jar");
							if (file.exists()) {
								return;
							} else {
								file = new File(pluginDir.getParentFile(), parentPlugin + ".war");
								if (file.exists()) {
									//子插件在另一个运行于父插件后的插件加载运行后静默返回
									return;
								} else {
									String msg = "忽略插件 " + pluginName + ": 父插件 " + parentPlugin
											+ " 不存在.";
									Log.warn(msg);
									System.out.println(msg);
									return;
								}
							}
						} else {
							String msg = "忽略插件 " + pluginName + ": 父插件 " + parentPlugin
									+ " 不存在.";
							Log.warn(msg);
							System.out.println(msg);
							return;
						}
					}
				}
				// 这不是子插件，因此创建新的类加载器。
				else {
					pluginLoader = new PluginClassLoader();
					pluginLoader.addDirectory(pluginDir, classesDir != null);
				}

				// 检查如果该插件启用开发模式. 如果是，则配置开发模式。
				PluginDevEnvironment dev = null;
				if (webRoot != null || classesDir != null) {
					dev = new PluginDevEnvironment();

					System.out.println("Plugin " + pluginName + " 在开发模式下运行.");
					Log.info("Plugin " + pluginName + " 在开发模式下运行.");
					if (webRoot != null) {
						File webRootDir = new File(webRoot);
						if (!webRootDir.exists()) {
							webRootDir = new File(pluginDir, webRoot);
						}

						if (webRootDir.exists()) {
							dev.setWebRoot(webRootDir);
						}
					}

					if (classesDir != null) {
						File classes = new File(classesDir);
						if (!classes.exists()) {
							classes = new File(pluginDir, classesDir);
						}

						if (classes.exists()) {
							dev.setClassesDir(classes);
							pluginLoader.addURLFile(classes.getAbsoluteFile().toURL());
						}
					}
				}

				String className = pluginXML.selectSingleNode("/plugin/class").getText().trim();
				plugin = (Plugin) pluginLoader.loadClass(className).newInstance();
				if (parentPluginNode != null) {
					String parentPlugin = parentPluginNode.getTextTrim();
					// 是否已加载父插件
					if (plugins.containsKey(parentPlugin)) {
						pluginLoader = classloaders.get(getPlugin(parentPlugin));
						classloaders.put(plugin, pluginLoader);
					}
				}

				plugins.put(pluginName, plugin);
				pluginDirs.put(plugin, pluginDir);

				// 如果这是一个子插件，则注册该插件.
				if (parentPluginNode != null) {
					String parentPlugin = parentPluginNode.getTextTrim();
					List<String> childrenPlugins = parentPluginMap.get(plugins.get(parentPlugin));
					if (childrenPlugins == null) {
						childrenPlugins = new ArrayList<String>();
						parentPluginMap.put(plugins.get(parentPlugin), childrenPlugins);
					}
					childrenPlugins.add(pluginName);
					// 注册父子插件的关系
					childPluginMap.put(plugin, parentPlugin);
				} else {
					// 不是子插件的情况下注册该插件
					classloaders.put(plugin, pluginLoader);
				}

				// Check the plugin's database schema (if it requires one).
				//				if (!DbConnectionManager.getSchemaManager().checkPluginSchema(plugin)) {
				//					// The schema was not there and auto-upgrade failed.
				//					Log.error(pluginName + " - " + LocaleUtils.getLocalizedString("upgrade.database.failure"));
				//					System.out.println(pluginName + " - " + LocaleUtils.getLocalizedString("upgrade.database.failure"));
				//				}

				// 加载插件中定义的所有 JSP。
				File webXML = new File(pluginDir, "web" + File.separator + "WEB-INF" + File.separator + "web.xml");
				if (webXML.exists()) {
					PluginServlet.registerServlets(this, plugin, webXML);
				}
				// 加载任何自定义的servlet。
				File customWebXML = new File(pluginDir, "web" + File.separator + "WEB-INF" + File.separator
						+ "web-custom.xml");
				if (customWebXML.exists()) {
					PluginServlet.registerServlets(this, plugin, customWebXML);
				}

				if (dev != null) {
					pluginDevelopment.put(plugin, dev);
				}

				// 初始化插件
				ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
				Thread.currentThread().setContextClassLoader(pluginLoader);
				plugin.initializePlugin(this, pluginDir);
				Thread.currentThread().setContextClassLoader(oldLoader);

				// 如果定义有<adminconsole>节点，则注册。
				Element adminElement = (Element) pluginXML.selectSingleNode("/plugin/adminconsole");
				if (adminElement != null) {
					Element appName = (Element) adminElement.selectSingleNode("/plugin/adminconsole/global/appname");
					if (appName != null) {
						// 设置插件名称，适当的时候加载i18n字符串。
						appName.addAttribute("plugin", pluginName);
					}
					// 如果指定了全局的 images，则重写它的URL.
					Element imageEl = (Element) adminElement.selectSingleNode("/plugin/adminconsole/global/logo-image");
					if (imageEl != null) {
						imageEl.setText("plugins/" + pluginName + "/" + imageEl.getText());
						// 设置插件名称，适当的时候加载i18n字符串。
						imageEl.addAttribute("plugin", pluginName);
					}
					imageEl = (Element) adminElement.selectSingleNode("/plugin/adminconsole/global/login-image");
					if (imageEl != null) {
						imageEl.setText("plugins/" + pluginName + "/" + imageEl.getText());
						// 设置插件名称，适当的时候加载i18n字符串。
						imageEl.addAttribute("plugin", pluginName);
					}
					//修改XML中所有的URL，使他们通过插件servlet的正确传递。
					List urls = adminElement.selectNodes("//@url");
					for (Object url : urls) {
						Attribute attr = (Attribute) url;
						attr.setValue("plugins/" + pluginName + "/" + attr.getValue());
					}
					String[] elementNames = new String[] { "tab", "sidebar", "item" };
					for (String elementName : elementNames) {
						List values = adminElement.selectNodes("//" + elementName);
						for (Object value : values) {
							Element element = (Element) value;
							if (element.attribute("name") != null || element.attribute("value") != null) {
								element.addAttribute("plugin", pluginName);
							}
						}
					}
					//FIXME:///Y-N 注解掉了
					//					AdminConsole.addModel(pluginName, adminElement);
				}
				firePluginCreatedEvent(pluginName, plugin);
			} else {
				Log.warn("插件 " + pluginDir + " 不能加载: plugin.xml 文件没有找到");
			}
		} catch (Throwable e) {
			Log.error("加载插件错误: " + pluginDir, e);
		}
	}

	private void firePluginCreatedEvent(String name, Plugin plugin) {
		for (PluginListener listener : pluginListeners) {
			listener.pluginCreated(name, plugin);
		}
	}

	private void firePluginsMonitored() {
		for (PluginManagerListener listener : pluginManagerListeners) {
			listener.pluginsMonitored();
		}
	}

	/**
	 * <p>卸载插件方法. The {@link Plugin#destroyPlugin()} 
	 * 该方法被调用时，其它所有资源将被释放. </br>
	 * 名称应该是插件目录的名称,而不是作为插件元数据名称</br>
	 * 该方法仅仅是移除一个插件，但不会删除该插件的JAR文件</br>
	 * 如果这个JAR插件仍然存在并且这个方法在后面被调用了，那么这个插件将会在下一次插件监听程序中运行.</br>
	 * <p/>
	 * 当插件文件删除时该方法会自动被调用
	 *
	 * @param pluginName 要卸载的插件名称.
	 */
	public void unloadPlugin(String pluginName) {
		Log.debug("PluginManager: 卸载插件 " + pluginName);

		Plugin plugin = plugins.get(pluginName);
		if (plugin != null) {
			// 如果它存在从开发模式删除.
			pluginDevelopment.remove(plugin);

			//查看是否有子插件被定义
			if (parentPluginMap.containsKey(plugin)) {
				String[] childPlugins = parentPluginMap.get(plugin).toArray(
						new String[parentPluginMap.get(plugin).size()]);
				parentPluginMap.remove(plugin);
				for (String childPlugin : childPlugins) {
					Log.debug("卸载子插件: " + childPlugin);
					childPluginMap.remove(plugins.get(childPlugin));
					unloadPlugin(childPlugin);
				}
			}

			File webXML = new File(pluginDirectory, pluginName + File.separator + "web" + File.separator + "WEB-INF"
					+ File.separator + "web.xml");
			if (webXML.exists()) {
				//FIXME:Y-N注掉了
				//AdminConsole.removeModel(pluginName);
				PluginServlet.unregisterServlets(webXML);
			}
			File customWebXML = new File(pluginDirectory, pluginName + File.separator + "web" + File.separator
					+ "WEB-INF" + File.separator + "web-custom.xml");
			if (customWebXML.exists()) {
				PluginServlet.unregisterServlets(customWebXML);
			}

			// 包装一个消毁插件的try/catch块. 否则如果在消毁过程中产生异常将会破坏整个销毁过程
			// 在这种情况下，可能需要重新启动JAWA服务来全面清理插件资源。
			try {
				plugin.destroyPlugin();
			} catch (Exception e) {
				Log.error(e.getMessage(), e);
			}
		}

		// 移除插件引用以便从内存中卸载掉
		// 如果移除失败，必须重新添加原来的插件引用关系
		// 如果移除失败，管理控制台会几秒钟看不到这个插件，但刷新之后该插件就会显示出来
		plugins.remove(pluginName);
		File pluginFile = pluginDirs.remove(plugin);
		PluginClassLoader pluginLoader = classloaders.remove(plugin);

		// 当插件被打开时尝试移除该文件夹. 如果顺利执行那么插件将会被成功移除
		// 否则被插件创建的对象仍然在内存中存在.
		File dir = new File(pluginDirectory, pluginName);
		// 卸载插件限时2秒钟.
		try {
			Thread.sleep(2000);
			// 要求系统清理引用
			System.gc();
			int count = 0;
			while (!deleteDir(dir) && count < 5) {
				Log.warn("Error unloading plugin " + pluginName + ". " + "Will attempt again momentarily.");
				Thread.sleep(8000);
				count++;
				// 要求系统清理引用
				System.gc();
			}
		} catch (InterruptedException e) {
			Log.error(e.getMessage(), e);
		}

		if (plugin != null && !dir.exists()) {
			//检查是否为一个子插件，如果是则将父插件一起卸载.
			if (childPluginMap.containsKey(plugin)) {
				String parentPluginName = childPluginMap.get(plugin);
				Plugin parentPlugin = plugins.get(parentPluginName);
				List<String> childrenPlugins = parentPluginMap.get(parentPlugin);

				childrenPlugins.remove(pluginName);
				childPluginMap.remove(plugin);

				/**
				 * 当实现PluginListener的父插件在其子插件前销毁，将不会调用它的pluginDestroyed()方法
				 */
				if (parentPlugin instanceof PluginListener) {
					PluginListener listener;
					listener = (PluginListener) parentPlugin;
					listener.pluginDestroyed(pluginName, plugin);
				}
				unloadPlugin(parentPluginName);
			}
			firePluginDestroyedEvent(pluginName, plugin);
		} else if (plugin != null) {
			//卸载失败时恢复之前的引用关系
			plugins.put(pluginName, plugin);
			pluginDirs.put(plugin, pluginFile);
			classloaders.put(plugin, pluginLoader);
		}
	}

	private void firePluginDestroyedEvent(String name, Plugin plugin) {
		for (PluginListener listener : pluginListeners) {
			listener.pluginDestroyed(name, plugin);
		}
	}

	/**
	 * 从一个插件类加载器加载一个类。
	 *
	 * @param plugin 插件实例.
	 * @param className 要加载的类名.
	 * @return 返回加载后的类.
	 * @throws ClassNotFoundException 类不存在异常.
	 * @throws IllegalAccessException 无权访问异常.
	 * @throws InstantiationException 该类没有创建时异常.
	 */
	public Class loadClass(Plugin plugin, String className) throws ClassNotFoundException, IllegalAccessException,
			InstantiationException {
		PluginClassLoader loader = classloaders.get(plugin);
		return loader.loadClass(className);
	}

	/**
	 * 如果插件中启用开发模式，则返回一个插件的开发环境。
	 * @param plugin 插件接口实例.
	 * @return 插件的开发环境,如果没有启用则返回 <tt>null</tt>
	 */
	public PluginDevEnvironment getDevEnvironment(Plugin plugin) {
		return pluginDevelopment.get(plugin);
	}

	/**
	 * 返回插件的名称. 该值从 plugin.xml文件或插件中得到. 如果该值没找到则返回 <tt>null</tt>.
	 * 该值和插件目录中的名称是截然不同的
	 * @param plugin 插件接口实例.
	 * @return 插件名称.
	 */
	public String getName(Plugin plugin) {
		String name = getElementValue(plugin, "/plugin/name");
		String pluginName = pluginDirs.get(plugin).getName();
		if (name != null) {
			//FIXME: Y-N MAKE
			//return AdminConsole.getAdminText(name, pluginName);
			return "";
		} else {
			return pluginName;
		}
	}

	/**
	 * 返回插件描述. 
	 * 该值从　plugin.xml文件或插件中得到. 如果未找到则返回<tt>null</tt>
	 *
	 * @param plugin 插件接口实例.
	 * @return 插件描述.
	 */
	public String getDescription(Plugin plugin) {
		String pluginName = pluginDirs.get(plugin).getName();
		return "";
		//FIXME:Y-N MAKE
		//return AdminConsole.getAdminText(getElementValue(plugin, "/plugin/description"), pluginName);
	}

	/**
	 * 返回插件的作者. 该值从　plugin.xml文件或插件中得到. 如果未找到则返回<tt>null</tt>
	 *
	 * @param plugin 插件接口实例.
	 * @return 插件的作者.
	 */
	public String getAuthor(Plugin plugin) {
		return getElementValue(plugin, "/plugin/author");
	}

	/**
	 * 获取插件版本. 该值从　plugin.xml文件或插件中得到. 如果未找到则返回<tt>null</tt>
	 *
	 * @param plugin 插件接口实例.
	 * @return 插件版本.
	 */
	public String getVersion(Plugin plugin) {
		return getElementValue(plugin, "/plugin/version");
	}

	/**
	 * 返回这个插件可以运行的最小的服务器版本
	 * 该值从　plugin.xml文件或插件中得到. 如果未找到则返回<tt>null</tt>
	 *
	 * @param plugin 插件接口实例.
	 * @return 最小的服务器版本
	 */
	public String getMinServerVersion(Plugin plugin) {
		return getElementValue(plugin, "/plugin/minServerVersion");
	}

	/**
	 * 返回插件的数据库实例, 
	 * 如果存在，该值从　plugin.xml文件或插件中得到. 如果未找到则返回<tt>null</tt>
	 *
	 * @param plugin 插件接口实例.
	 * @return 插件的数据库实例，如果不存在返回<tt>null</tt>
	 */
	public String getDatabaseKey(Plugin plugin) {
		return getElementValue(plugin, "/plugin/databaseKey");
	}

	/**
	 * 返回插件中数据库实例的版本, 如果存在，该值从　plugin.xml文件或插件中得到.</br> 
	 * 如果未找到则返回<tt>-1</tt>
	 *
	 * @param plugin 插件接口实例.
	 * @return 插件中数据库实例的版本，若不存在返回 <tt>-1</tt>.
	 */
	public int getDatabaseVersion(Plugin plugin) {
		String versionString = getElementValue(plugin, "/plugin/databaseVersion");
		if (versionString != null) {
			try {
				return Integer.parseInt(versionString.trim());
			} catch (NumberFormatException nfe) {
				Log.error(nfe.getMessage(), nfe);
			}
		}
		return -1;
	}

	/**
	 * 返回插件许可协议类型
	 *
	 * @param plugin 插件接口实例.
	 * @return 插件许可协议
	 */
	public License getLicense(Plugin plugin) {
		String licenseString = getElementValue(plugin, "/plugin/licenseType");
		if (licenseString != null) {
			try {
				// 试图加载得到的许可类型.
				return License.valueOf(licenseString.toLowerCase().trim());
			} catch (IllegalArgumentException iae) {
				Log.error(iae.getMessage(), iae);
			}
		}
		return License.other;
	}

	/**
	 * 返回插件的类加载器.
	 *
	 * @param plugin 插件接口实例.
	 * @return 插件的类加载器.
	 */
	public PluginClassLoader getPluginClassloader(Plugin plugin) {
		return classloaders.get(plugin);
	}

	/**
	 * 从插件的plugin.xml文件中用的XPath表达式获取一个元素的值。
	 *
	 * @param plugin 插件接口实例.
	 * @param xpath  XPath表达式
	 * @return XPath表达式所对应的值.
	 */
	private String getElementValue(Plugin plugin, String xpath) {
		File pluginDir = pluginDirs.get(plugin);
		if (pluginDir == null) {
			return null;
		}
		try {
			File pluginConfig = new File(pluginDir, "plugin.xml");
			if (pluginConfig.exists()) {
				SAXReader saxReader = new SAXReader();
				saxReader.setEncoding("UTF-8");
				Document pluginXML = saxReader.read(pluginConfig);
				Element element = (Element) pluginXML.selectSingleNode(xpath);
				if (element != null) {
					return element.getTextTrim();
				}
			}
		} catch (Exception e) {
			Log.error(e.getMessage(), e);
		}
		return null;
	}

	/**
	 *　插件协议类型.
	 */
	// Support for QDox Parser
	public enum License {

		/**
		 * 商业许可.
		 */
		commercial,

		/**
		 * 分配使用的GNU通用公共许可证（GPL）.
		 */
		gpl,

		/**
		 * 插件使用Apache许可.
		 */
		apache,

		/**
		 * 该插件只在一个组织内部使用，不重新分配。
		 */
		internal,

		/**
		 * 其它类型的许可
		 */
		other;
	}

	/**
	 * 监控插件目录,用于发现新的插件或是更新插件.
	 */
	private class PluginMonitor implements Runnable {

		private boolean running = false;

		private boolean executed = false;

		private boolean firstRun = true;

		public void run() {
			// 如果正在运行,退出.
			synchronized (this) {
				if (running) {
					return;
				}
				running = true;
			}
			try {
				running = true;

				File[] jars = pluginDirectory.listFiles(new FileFilter() {
					public boolean accept(File pathname) {
						String fileName = pathname.getName().toLowerCase();
						return (fileName.endsWith(".jar") || fileName.endsWith(".war"));
					}
				});

				if (jars == null) {
					return;
				}

				for (File jarFile : jars) {
					String pluginName = jarFile.getName().substring(0, jarFile.getName().length() - 4).toLowerCase();
					// 插件是否已解压.
					File dir = new File(pluginDirectory, pluginName);
					pluginFiles.put(pluginName, jarFile);
					// 如果没有解压,解压插件.
					if (!dir.exists()) {
						unzipPlugin(pluginName, jarFile, dir);
					}
					// 插件是否需要更新.
					else if (jarFile.lastModified() > dir.lastModified()) {
						// 如果是第一次运行,那么插件并没有启动,直接删除.
						if (firstRun) {
							int count = 0;
							while (!deleteDir(dir) && count < 5) {
								Thread.sleep(1000);
							}
						} else {
							//卸载插件
							unloadPlugin(pluginName);
						}
						// 重新解压插件.
						if (!dir.exists()) {
							unzipPlugin(pluginName, jarFile, dir);
						}
					}
				}

				File[] dirs = pluginDirectory.listFiles(new FileFilter() {
					public boolean accept(File pathname) {
						return pathname.isDirectory();
					}
				});

				// admin 插件需要首先启动
				Arrays.sort(dirs, new Comparator<File>() {
					public int compare(File file1, File file2) {
						if (file1.getName().equals("admin")) {
							return -1;
						} else if (file2.getName().equals("admin")) {
							return 1;
						} else {
							return file1.compareTo(file2);
						}
					}
				});

				Set<String> jarSet = new HashSet<String>();
				for (File file : jars) {
					jarSet.add(file.getName().toLowerCase());
				}

				// 删除要更新插件目录
				List<String> toDelete = new ArrayList<String>();
				for (File pluginDir : dirs) {
					String pluginName = pluginDir.getName();
					if (pluginName.equals("admin")) {
						continue;
					}
					if (!jarSet.contains(pluginName + ".jar")) {
						if (!jarSet.contains(pluginName + ".war")) {
							toDelete.add(pluginName);
						}
					}
				}
				for (String pluginName : toDelete) {
					unloadPlugin(pluginName);
				}

				// 加载所有要启动的插件.
				for (File dirFile : dirs) {
					if (dirFile.exists() && !plugins.containsKey(dirFile.getName())) {
						loadPlugin(dirFile);
					}
				}

				if (!JawaServer.getInstance().isSetupMode()) {
					executed = true;
				}

				firePluginsMonitored();
			} catch (Throwable e) {
				Log.error(e.getMessage(), e);
			}

			synchronized (this) {
				running = false;
			}

			firstRun = false;
		}

		/**
		 * 从JAR文件解压插件到相应的目录中. 如果该JAR文件不存在，则该方法什么也不执行.
		 *
		 * @param pluginName 插件的名称.
		 * @param file JAR文件
		 * @param dir 要将JAR文件提取到哪个目录.
		 */
		private void unzipPlugin(String pluginName, File file, File dir) {
			try {
				ZipFile zipFile = new JarFile(file);
				// 判断是否是一个合法的插件文件包
				if (zipFile.getEntry("plugin.xml") == null) {
					return;
				}
				dir.mkdir();
				dir.setLastModified(file.lastModified());
				Log.debug("PluginManager: 解压插件: " + pluginName);
				for (Enumeration e = zipFile.entries(); e.hasMoreElements();) {
					JarEntry entry = (JarEntry) e.nextElement();
					File entryFile = new File(dir, entry.getName());
					// 忽略任何MANIFEST.MF条目。
					if (entry.getName().toLowerCase().endsWith("manifest.mf")) {
						continue;
					}
					if (!entry.isDirectory()) {
						entryFile.getParentFile().mkdirs();
						FileOutputStream out = new FileOutputStream(entryFile);
						InputStream zin = zipFile.getInputStream(entry);
						byte[] b = new byte[512];
						int len;
						while ((len = zin.read(b)) != -1) {
							out.write(b, 0, len);
						}
						out.flush();
						out.close();
						zin.close();
					}
				}
				zipFile.close();

				//如果插件的lib目录可能包含Pack200 JAR文件的版本，则将其解压
				unpackArchives(new File(dir, "lib"));
			} catch (Exception e) {
				Log.error(e.getMessage(), e);
			}
		}

		/**
		 * <p>将任何包文件转换成标准的JAR文件的目录. </p>
		 * <p>每包文件被转换为一个JAR文件将被删除后. </p>
		 * <p>如果包文件没有找到，则该方法直接返回不做处理.</p>
		 *
		 * @param libDir 要转换的文件目录.
		 */
		private void unpackArchives(File libDir) {
			//从lib目录得到一个包文件列表
			File[] packedFiles = libDir.listFiles(new FilenameFilter() {
				public boolean accept(File dir, String name) {
					return name.endsWith(".pack");
				}
			});

			if (packedFiles == null) {
				// 如果.pack文件不存在直接返回不做处理
				return;
			}

			// 打开每个文件.
			for (File packedFile : packedFiles) {
				try {
					String jarName = packedFile.getName()
							.substring(0, packedFile.getName().length() - ".pack".length());
					// 删除存在的同名JAR文件.
					File jarFile = new File(libDir, jarName);
					if (jarFile.exists()) {
						jarFile.delete();
					}

					InputStream in = new BufferedInputStream(new FileInputStream(packedFile));
					JarOutputStream out = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(new File(
							libDir, jarName))));
					Pack200.Unpacker unpacker = Pack200.newUnpacker();
					//调用解包方法
					unpacker.unpack(in, out);

					in.close();
					out.close();
					packedFile.delete();
				} catch (Exception e) {
					Log.error(e.getMessage(), e);
				}
			}
		}

	}

	/**
	 * 删除一个目录.
	 *
	 * @param dir 要删除的目录.
	 * @return true 删除成功则返回true.
	 */
	private boolean deleteDir(File dir) {
		if (dir.isDirectory()) {
			String[] childDirs = dir.list();
			//始终删除JAR文件，删除前先对lib目录排序
			List<String> children = new ArrayList<String>(Arrays.asList(childDirs));
			Collections.sort(children, new Comparator<String>() {
				public int compare(String o1, String o2) {
					if (o1.equals("lib")) {
						return -1;
					}
					if (o2.equals("lib")) {
						return 1;
					} else {
						return o1.compareTo(o2);
					}
				}
			});
			for (String file : children) {
				boolean success = deleteDir(new File(dir, file));
				if (!success) {
					Log.debug("PluginManager: 插件移除: 不能删除: " + new File(dir, file));
					return false;
				}
			}
		}
		boolean deleted = !dir.exists() || dir.delete();
		if (deleted) {
			// 删除创建的插件文件夹的JAR/ WAR文件
			pluginFiles.remove(dir.getName());
		}
		return deleted;
	}

	public void addPluginListener(PluginListener listener) {
		pluginListeners.add(listener);
	}

	public void removePluginListener(PluginListener listener) {
		pluginListeners.remove(listener);
	}

	public void addPluginManagerListener(PluginManagerListener listener) {
		pluginManagerListeners.add(listener);
		if (isExecuted()) {
			firePluginsMonitored();
		}
	}

	public void removePluginManagerListener(PluginManagerListener listener) {
		pluginManagerListeners.remove(listener);
	}
}